export const prerender = false; import type { APIRoute } from "astro"; import { and, eq, sql } from "drizzle-orm"; import { createDb } from "@/db"; import { comments, commentVotes, events, profiles } from "@/db/schema"; import { isUuid } from "@/lib/sanitize"; export const POST: APIRoute = async ({ params, request, locals }) => { const runtime = locals.runtime as { env: { DATABASE_URL: string } }; const db = createDb(runtime.env.DATABASE_URL); const authHeader = request.headers.get("Authorization"); if (!authHeader?.startsWith("Bearer ")) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const apiKey = authHeader.slice(7); const apiKeyHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(apiKey)).then((buf) => Array.from(new Uint8Array(buf)) .map((b) => b.toString(16).padStart(2, "0")) .join(""), ); const [profile] = await db .select({ id: profiles.id, active: profiles.active }) .from(profiles) .where(eq(profiles.apiKeyHash, apiKeyHash)); if (!profile || !profile.active) { return Response.json({ error: "Invalid API key" }, { status: 401 }); } const commentId = params.id; if (!commentId || !isUuid(commentId)) { return Response.json({ error: "Invalid comment ID" }, { status: 400 }); } const ct = request.headers.get("Content-Type")?.split(";")[0].trim(); if (ct !== "application/json") { return Response.json({ error: "Content-Type must be application/json" }, { status: 415 }); } let body: { vote?: string }; try { body = await request.json(); } catch { return Response.json({ error: "Invalid JSON body" }, { status: 400 }); } const { vote } = body; if (vote !== "up" && vote !== "down") { return Response.json({ error: "Invalid vote" }, { status: 400 }); } const [comment] = await db .select({ id: comments.id, eventActive: events.active }) .from(comments) .innerJoin(events, eq(comments.eventId, events.id)) .where(eq(comments.id, commentId)); if (!comment) { return Response.json({ error: "Comment not found" }, { status: 404 }); } if (!comment.eventActive) { return Response.json({ error: "Event is resolved" }, { status: 403 }); } const [existingVote] = await db .select({ vote: commentVotes.vote }) .from(commentVotes) .where(and(eq(commentVotes.commentId, commentId), eq(commentVotes.profileId, profile.id))); const oldValue = existingVote ? (existingVote.vote === "up" ? 1 : -1) : 0; const newValue = vote === "up" ? 1 : -1; const scoreDelta = newValue - oldValue; if (existingVote) { await db .update(commentVotes) .set({ vote }) .where(and(eq(commentVotes.commentId, commentId), eq(commentVotes.profileId, profile.id))); } else { await db.insert(commentVotes).values({ commentId, profileId: profile.id, vote, }); } if (scoreDelta !== 0) { await db .update(comments) .set({ score: sql`${comments.score} + ${scoreDelta}` }) .where(eq(comments.id, commentId)); } const [updated] = await db.select({ score: comments.score }).from(comments).where(eq(comments.id, commentId)); return Response.json({ score: updated?.score ?? 0 }); };